//
// WriteCodeFragment.cs
//
// Authors:
//	Marek Safar  <marek.safar@gmail.com>
//
// Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//


using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.Build.Tasks
{
	public class WriteCodeFragment : TaskExtension
	{
		public ITaskItem[] AssemblyAttributes { get; set; }

		[Required]
		public string Language { get; set; }

		public ITaskItem OutputDirectory { get; set; }

		[Output]
		public ITaskItem OutputFile { get; set; }

		public override bool Execute ()
		{
		    if (OutputFile == null) {
		    	if (OutputDirectory == null)
					return false;

				throw new NotImplementedException ("Temporary file in custom directory");
			} else {
				if (OutputDirectory != null)
					OutputFile = new TaskItem (Path.Combine (OutputDirectory.ItemSpec, OutputFile.ItemSpec));
			}

			if (AssemblyAttributes == null) {
				OutputFile = null;
				return true;
			}

			var file = OutputFile.ItemSpec;
			if (!GenerateFile (file))
				return false;

			Log.LogMessage ("Emitted specified code into \"{0}\".", file);
			return true;
		}

		bool GenerateFile (string file)
		{
			var cu = new CodeCompileUnit ();
			var ns = new CodeNamespace ();
			cu.Namespaces.Add (ns);

			ns.Imports.Add (new CodeNamespaceImport ("System"));
			ns.Imports.Add (new CodeNamespaceImport ("System.Reflection"));
			ns.Comments.Add (new CodeCommentStatement ("Generated by the WriteCodeFragment task on " + DateTime.Now));

			foreach (var attr in AssemblyAttributes) {
				// There is no better way to get ordered parameter data
				// that this horrible api
				var metadata = attr.CloneCustomMetadata ();
				List<Tuple<int, CodeAttributeArgument>> args = null;
				foreach (DictionaryEntry entry in metadata) {
					var key = (string) entry.Key;
					if (!key.StartsWith ("_Parameter", StringComparison.Ordinal))
						continue;

					int key_value;
					if (!int.TryParse (key.Substring (10), NumberStyles.None, CultureInfo.InvariantCulture, out key_value))
						continue;

					if (args == null)
						args = new List<Tuple<int, CodeAttributeArgument>> ();

					args.Add (Tuple.Create (key_value, new CodeAttributeArgument (new CodePrimitiveExpression ((string) entry.Value))));
				}

				var cad = new CodeAttributeDeclaration (attr.ItemSpec);
				if (args != null) {
					args.Sort ((a, b) => a.Item1.CompareTo (b.Item1));
					foreach (var arg in args)
						cad.Arguments.Add (arg.Item2);
				}

				cu.AssemblyCustomAttributes.Add (cad);
			}

			var provider = CodeDomProvider.CreateProvider (Language);
			using (var sw = new StreamWriter (file, false)) {
				provider.GenerateCodeFromCompileUnit (cu, sw, new CodeGeneratorOptions ());
			}

			return true;
		}
	}
}

